﻿/*======================================================================
== Copyright : BlueCurve (c)
== Licence   : Gnu/GPL v2.x
== Author    : Teddy Albina
== Email     : bluecurveteam@gmail.com
== Web site  : http://www.codeplex.com/BlueCurve
========================================================================*/
using System;
using System.Collections.Generic;
using System.Threading;
using BlueCurve.Common;
using System.Runtime.InteropServices;

namespace BlueCurve.MemoryCache
{
    [Serializable]
    [ComVisible(false)]
    public class KeysCache<Tvalue, Tid, Tdata> : MemoryCache.IKeysCache<Tvalue, Tid, Tdata>
    {
        #region 'Configuration'

        /// <summary>
        /// Durée de vie des données en secondes
        /// </summary>
        public int LifeTime { get; set; }
        /// <summary>
        /// Permets de laisse tourner ou nom le threads
        /// </summary>
        private bool IsRun = false;
        /// <summary>
        /// Timer de gestion du cache
        /// </summary>
        private System.Timers.Timer _timer = new System.Timers.Timer();
        /// <summary>
        /// Dictionnaire contenant les données mises en cache
        /// </summary>
        private Dictionary<Tvalue, ValueFields<Tid, DateTime>> CachedObjects = new Dictionary<Tvalue, ValueFields<Tid, DateTime>>();
        /// <summary>
        /// Dictionnaire contenant les clefs mises en cache
        /// </summary>
        private ValuesCache<Tid, Tdata> CachedValues = new ValuesCache<Tid, Tdata>();
        /// <summary>
        /// Locking object
        /// </summary>
        private readonly ReaderWriterLockSlim Threadslock = new ReaderWriterLockSlim();
        public static event EventHandler StopTheCache;


        public KeysCache()
        {
            _timer.Interval = 5 * 1000;
            _timer.Elapsed += new System.Timers.ElapsedEventHandler(_timer_Elapsed);
            _timer.Start();
        }

        #endregion




        #region 'Function'
        
        /// <summary>
        /// Ajoute des données dans le cache
        /// </summary>
        /// <param name="value">Données à mettre dans le cache</param>
        /// <param name="id">Identifiant des données à ajouter</param>
        /// <param name="data">Valeur à mettre dans le dictionnaire de "clefs"</param>
        public void Add(Tvalue value, Tid id, Tdata data)
        {
            Threadslock.EnterUpgradeableReadLock();
            try
            {
                if (!CachedObjects.ContainsKey(value))
                {
                    Threadslock.EnterWriteLock();
                    try
                    {
                        CachedObjects.Add(value, new ValueFields<Tid, DateTime>(id, DateTime.Now));
                    }
                    finally
                    {
                        Threadslock.ExitWriteLock();
                    }
                }
                if (!CachedValues.Exist(id))
                {
                    Threadslock.EnterWriteLock();
                    try
                    {
                        CachedValues.Add(id, data);
                    }
                    finally
                    {
                        Threadslock.ExitWriteLock();
                    }
                }
            }
            finally
            {
                Threadslock.ExitUpgradeableReadLock();
            }
        }



        /// <summary>
        /// Permet d'obtenir un élément du cache
        /// </summary>
        /// <param name="value">Valeur de l'élément à recherche</param>
        /// <returns>Tdata</returns>
        public Tdata Get(Tvalue value)
        {
            Threadslock.EnterReadLock();
            try
            {
                if (!CachedObjects.ContainsKey(value))
                    return default(Tdata);
                else
                    return CachedValues.Get(CachedObjects[value].Value);
            }
            finally
            {
                Threadslock.ExitReadLock();
            }
        }


        /// <summary>
        /// Teste si l'élément existe dans le cache
        /// </summary>
        /// <param name="value">Element à teste</param>
        /// <returns>bool</returns>
        public bool Exist(Tvalue value)
        {
            Threadslock.EnterReadLock();
            try
            {
                return CachedObjects.ContainsKey(value);
            }
            finally
            {
                Threadslock.ExitReadLock();
            }
        }
        

        /// <summary>
        /// Permet de vider le cache
        /// </summary>
        public void Clear()
        {
            Threadslock.EnterWriteLock();
            try
            {
                CachedObjects.Clear();
                CachedValues.Clear();
            }
            finally
            {
                Threadslock.ExitWriteLock();
            }
        }


        public void Stop()
        {
            this._timer.Stop();
        }



        private void _timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            if (!IsRun)
            {
                IsRun = true;
                Threadslock.EnterUpgradeableReadLock();
                try
                {
                    List<KeyValuePair<Tid, Tvalue>> Temp = new List<KeyValuePair<Tid, Tvalue>>();
                    foreach (KeyValuePair<Tvalue, ValueFields<Tid, DateTime>> pair in CachedObjects)
                    {
                        if (DateTime.Now.Subtract(pair.Value.Id).Seconds >= LifeTime)
                        {
                            Temp.Add(new KeyValuePair<Tid, Tvalue>(pair.Value.Value, pair.Key));
                        }
                    }

                    Threadslock.EnterWriteLock();
                    try
                    {
                        Temp.ForEach(
                        delegate(KeyValuePair<Tid, Tvalue> pair)
                        {
                            CachedObjects.Remove(pair.Value);
                            CachedValues.Remove(pair.Key);
                        });
                    }
                    finally
                    {
                        Threadslock.ExitWriteLock();
                    }
                }
                finally
                {
                    Threadslock.ExitUpgradeableReadLock();
                }
                IsRun = false;
            }
        }

        #endregion




        #region 'Event'

        private void StopCache_StopTheCache(object sender, EventArgs e)
        {
            this._timer.Stop();
        }

        #endregion
    }
}
